package client;

import java.awt.Point;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JPanel;

import DTO.AnnonceDTO;
import DTO.Capsule;
import DTO.ResumeAnnonceDTO;
import DTO.VendeurDTO;


/**
 * 
 * Le noyau client.
 * 
 * @author Julien Dubois, Valentin Brémond
 * @version 1.0 (Avril 2012)
 */
public class ClientCommunication 
{
	/**
	 * Adresse de connexion
	 */
	private static String adresse = "" ;
	
	/**
	 * La socket du client.
	 */
	private Socket client;
	
	/**
	 * Le buffer d'entrée.
	 */
	private ObjectInputStream in = null;
	
	/**
	 * Le buffer de sortie.
	 */
	private ObjectOutputStream out = null;
	
	/**
	 * Le singleton du noyau.
	 */
	//Permet de n'instancier qu'un seul objet de cette classe.
	private static ClientCommunication singleton;
	
	/**
	 * L'instance du vendeur courant.
	 */
	private VendeurDTO vendeurStocke;
	
	/**
	 * L'instance de l'annonce courante.
	 */
	private AnnonceDTO annonceStocke;
	
	

	//CONSTRUCTEUR
	
	/**
	 * Constructeur par défaut.
	 */
	public ClientCommunication()
	{
		//permet de ne créer qu'une seule fenêtre.
		singleton = this ; 
		
		vendeurStocke = new VendeurDTO();
		annonceStocke = new AnnonceDTO();
		
		try
		{
			client = new Socket(adresse, 8123);
		} 
		catch (UnknownHostException e)
		{
			e.printStackTrace();
		} 
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		
		try
		{
			this.out = new ObjectOutputStream(new BufferedOutputStream(this.client.getOutputStream()));
			
			// On flushe un coup pour que l'input du client puisse se lancer
			this.out.flush();
			
			this.in = new ObjectInputStream(new BufferedInputStream(this.client.getInputStream()));
		}
		catch(IOException ioE)
		{
			ioE.printStackTrace();
		}
		
		//Une fois la connection établie et les flux IO paramétrés, on lance notre page de Démarrage.
		new ClientFenetreDemarrage();	
	}


	//METHODES
	
	
	
	/**
	 * Méthode principale.
	 * 
	 * @param args
	 * 
	 */
	public static void main(String[] args)
	{
		/**
		 * Adresse de connexion.
		 */
		ClientCommunication.adresse = args.length > 0 ? ClientCommunication.adresse = args[0] : "localhost";
		new ClientCommunication();
	}

	
	

	/**
	 * Permet de récupérer l'instance unique de la classe {@link ClientCommunication}.
	 * 
	 * @return L'unique instance de la classe ClientCommunication.
	 */
	public static ClientCommunication getInstance()
	{
		if(ClientCommunication.singleton == null)
		{
			singleton = new ClientCommunication();
		}
		
		return singleton;
	}
	
	
	
	/**
	 * Permet de prendre la position de la fenêtre courante, la fermer et en en ouvrir une nouvelle,
	 * (en fonction du string passé en paramètre) exactement au même emplacement.
	 * 
	 * @param jpanel			 la fenetre courante à localiser sur l'écran.
	 * @param typeDeFenetre 	 le string de savoir quel type de fenêtre il faut créer.
	 */
	public void creerFenetre(JPanel jpanel, String typeDeFenetre)
	{
		//Pour ouvrir une fenêtre d'accueil avant connexion.   
		if (typeDeFenetre.equals("fenetreAccueil"))
		{
			// point p(x,y) la position à l'écran de la fenêtre courante.
			Point p = (jpanel.getLocationOnScreen());
			
			/*
			 * Pour que les deux fenêtres se superposent parfaitement, on ajuste les coordonnées de la seconde fenêtre.
			 * J'ai l'impression que la méthode getLocationOnScreen retourne la position (0,0) de la fenêtre, 
			 * A LINTERIEUR DE LA FENETRE et non la position (0,0) à l'extrême extrémité de la barre des tâche de la fenêtre.
			 * D'où ce léger réajustement manuel.
			 */
			p.y = p.y-25;
			p.x = p.x-2;
			

			//On indique à la nouvelle fenêtre où se positionner (en fonction de la position de la fenêtre précédente)
			(new ClientFenetreAccueil(false)).setLocation(p);
		}
		
		
		//Pour ouvrir une fenêtre d'accueil contenant le message "Votre compte a bien été supprimé".
		if (typeDeFenetre.equals("fenetreAccueilMessageErreur"))
		{
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientFenetreAccueil(true)).setLocation(p) ;
		}
		
		
		//Pour ouvrir une fenêtre de création de compte avant connection.
		if (typeDeFenetre.equals("fenetreCreerUnCompte"))
		{
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientCreerUnCompte()).setLocation(p) ;
		}
		
		
		//Pour ouvrir une fenêtre de création de compte après connection.
		if (typeDeFenetre.equals("fenetreCreerUnCompteDejaConnecte"))
		{
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientCreerUnCompteDejaConnecte()).setLocation(p) ;
		}
		
		
		//Pour ouvrir une fenêtre d'accueil après connection.
		if (typeDeFenetre.equals("fenetreUneFoisConnecte"))
		{
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientFenetreAccueilUneFoisConnecte()).setLocation(p);
		}
		
		
		//Pour ouvrir une fenêtre de gestion de compte (derrière le bouton "Accéder à mon compte").
		if (typeDeFenetre.equals("fenetreGestionDeCompte"))
		{
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientFenetreGestionDeCompte()).setLocation(p);
		}
		
		
		//Pour ouvrir une fenêtre de modification des informations des informations personnelles.
		//(derrière le bouton "Modifier mes informations")
		if (typeDeFenetre.equals("fenetreModificationInfoPerso"))
		{
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientFenetreModificationInfoPerso()).setLocation(p);
		}
		
		
		//Pour ouvrir une fenêtre permettant de poster une annonce.
		if (typeDeFenetre.equals("fenetrePosterAnnonce"))
		{
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientAnnoncePosterAnnonce()).setLocation(p);
		}
	}
	
	

	/**
	 * Surcharge la méthode {@link ClientCommunication#creerFenetre(JPanel jpanel, String typeDeFenetre)}
	 * Elle prend un paramètre de plus "idAnnonce" permettant de charger les paramètres de l'annonce avec laquelle l'on souhaite travailler.
	 * 
	 * @param jpanel				la fenetre courante à localiser sur l'écran.
	 * @param typeDeFenetre			le string de savoir quel type de fenêtre il faut créer.
	 * @param idAnnonce				l'id de l'annonce avec laquelle on désire travailler.		
	 */
	public void creerFenetre(JPanel jpanel, String typeDeFenetre, int idAnnonce)
	{
		
		//Pour ouvrir une fenêtre permettant d'afficher une annonce.
		if (typeDeFenetre.equals("fenetreVoirAnnonce"))
		{
			// l'instance de l'annonce correspondant à l'idAnnonce passé en paramètres.
			AnnonceDTO annonceRecup = this.recupererAnnonce(idAnnonce);
			setAnnonceStocke(annonceRecup);
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientAnnonceVoirAnnonce()).setLocation(p);
		}
		
		
		//Pour ouvrir une fenêtre permettant de modifier une annonce.
		if (typeDeFenetre.equals("fenetreModifierAnnonce"))
		{
			AnnonceDTO annonceRecup = this.recupererAnnonce(idAnnonce);
			setAnnonceStocke(annonceRecup);
			Point p = (jpanel.getLocationOnScreen()) ;
			p.y = p.y-25 ;
			p.x = p.x-2 ;
			(new ClientAnnonceModifierAnnonce()).setLocation(p);
		}
		
	}

	
	
	// METHODES CORRESPONDANTS AUX DIFFERENTES CAPSULES
	
	
	/**
	 * Permet de récupérer une annonce en fonction de l'idAnnonce passé en paramètre.
	 *	
	 * 
	 * @param idAnnonce		L'ID de l'annoce dont l'on souhaite récupérer l'instance.
	 * @return				une instance d'AnnonceDTO correspondant à l'idAnnonce passé en paramètre si elle est trouvée, une instance vide sinon.	
	 */
	public AnnonceDTO recupererAnnonce(int idAnnonce)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteRecupererAnnonce(idAnnonce);
		
		try
		{
			out.writeObject(capsule);
			out.flush();
		} 
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
		
		Capsule retour = null ;
		
		try
		{
			retour = (Capsule)in.readObject();
		
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.getAnnonce() ;
	}
	
	
	
	/**
	 * Retourne l'ID du vendeur si le login et le mot de passe concordent ; -1 sinon.
	 * 
	 * @param login				Login du vendeur
	 * @param mot_de_passe		Mot de passe du vendeur
	 * @return 					renvoie l'ID du vendeur si le combo login/mpd concourde, renvoie -1 sinon.
	 */
	public int connexionVendeur(String login, String mot_de_passe)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteConnexionVendeur(login, mot_de_passe);
		
		try
		{
			out.writeObject(capsule);
			out.flush();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ;
		
		try
		{
			retour = (Capsule)in.readObject();
		
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.getId() ;
	}
	
	
	/**
	 * Permet d'ajouter un vendeur.
	 * Retourne le nombre de lignes modifiées dans la BDD, sinon -1 si le vendeur existe déjà.
	 * 
	 * @param vendeur	L'instance du vendeur à ajouter dans la BDD.
	 * @return			Le nombre de lignes modifiées dans la BDD.
	 */
	public int ajouterVendeur(VendeurDTO vendeur)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteAjouterVendeur(vendeur);

		
		try
		{
			out.writeObject(capsule);
			out.flush();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ;
		
		try
		{
			retour = (Capsule)in.readObject();
		
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.getNombreLignesModifiees();

	}
	
	/**
	 * Permet de modifier les informations concernant un vendeur.
	 * 
	 * 
	 * @param id_vendeur	L'ID du vendeur qu'il faut remplacer.
	 * @param vendeur   	l'instance du vendeur à mettre à la place.
	 * @return				le nombre de lignes modifiées dans la BDD, sinon -1 si le vendeur n'existe pas
	 * 						ou -2 si le nouveau login est déjà utilisé.
	 */
	public int modifierVendeur(int id_vendeur, VendeurDTO vendeur)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteModifierVendeur(id_vendeur, vendeur);
		
		try
		{
			out.writeObject(capsule);
			out.flush();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ;
		
		try
		{
			retour = (Capsule)in.readObject();
		
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}

		
		if (retour.getNombreLignesModifiees() > 0)
		{
			vendeurStocke = vendeur ;
		}
		return retour.getNombreLignesModifiees();
	}
	
	
	
	/**
	 * Permet de modifier une annonce.
	 * 
	 * @param id_annonce	L'ID de l'annonce à mofidier.
	 * @param annonce		L'instance de l'annonce à mettre à la place.
	 * @return 				Retourne le nombre de lignes modifiées dans la BDD, sinon -1 si l'annonce n'existe pas 
	 * 						ou -2 si le vendeur associé n'existe plus.
	 */
	public int modifierAnnonce(int id_annonce, AnnonceDTO annonce)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteModifierAnnonce(id_annonce, annonce);

		try
		{
			out.writeObject(capsule);
			out.flush();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ;
		
		try
		{
			retour = (Capsule)in.readObject();
		
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}		

		return retour.getNombreLignesModifiees();
	}
	
	
	
	/**
	 * Retourne l'instance de VendeurDTO dont on a passé l'id_vendeur en paramètre. 
	 * 
	 * @param id_vendeur	L'ID du vendeur à récupérer.
	 * @return				Retourne le VendeurDTO associé au vendeur si le vendeur a été trouvé, 
	 * 						une instance de VendeurDTO vide sinon.
	 */
	public VendeurDTO recupererVendeur(int id_vendeur)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteRecupererVendeur(id_vendeur);
		
		try
		{
			out.writeObject(capsule);
			out.flush();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ;
		
		try
		{
			retour = (Capsule)in.readObject();
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.getVendeur();	
	}




	/**
	 * Supprime l'instance du vendeurdont l'id_vendeur est passé en paramètre.
	 * 
	 * @param id_vendeur	L'ID du vendeur à supprimer.
	 * @return				Retourne "true" si le vendeur a bien été supprimé, "false" sinon.
	 */
	public boolean supprimerVendeur(int id_vendeur)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteSupprimerVendeur(id_vendeur);
		
		try
		{
			out.writeObject(capsule);
			out.flush();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ;
		
		try
		{
			retour = (Capsule)in.readObject();
		
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.isFait();
	}
	
	
	/**
	 *	Ferme la connexion courante.
	 */
	public void fermerConnexion()
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteFermerConnexion();
		
		try
		{
			out.writeObject(capsule);
			out.flush();
		} 
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		try
		{
			out.close();
			in.close();
			client.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}

		System.exit(0);	
	}
	
	
	
	/**
	 * Récupère une liste contenant des instances de ResumeAnnonceDTO.
	 * 
	 * @return	Retourne une liste contenant des instances de ResumeAnnonceDTO.
	 */
	public List<ResumeAnnonceDTO> recupererToutesAnnonces()
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteRecupererToutesAnnonces() ;
		
		try
		{
			out.writeObject(capsule);
			out.flush();	
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ; 
		
		try
		{
			retour = (Capsule)in.readObject();
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.getListeResumeAnnonces() ;	
	}
	
	

	/**
	 * @param 	idVendeur l'ID du vendeur donc on souhaite recevoir les annonces associées.
	 * 
	 * @return 	Retourne une liste de toutes les annonces liées à l'ID du vendeur passé en paramètre
	 */
	public List<ResumeAnnonceDTO> recupererAnnoncesVendeur(int idVendeur)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteRecupererAnnoncesVendeur(idVendeur);
		
		try
		{
			out.writeObject(capsule);
			out.flush();	
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ; 
		
		try
		{
			retour = (Capsule)in.readObject();
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.getListeResumeAnnonces();	
	}
	
	
	
	/**
	 * Ajoute l'annoce dont l'instance est passée en paramètre.
	 * 
	 * @param annonce	Instance de l'annonce à ajouter.
	 * @return			Retourne le nombre de lignes modifiées dans la BDD. Pas d’erreur.
	 */
	public int ajouterAnnonce(AnnonceDTO annonce)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteAjouterAnnonce(annonce);
		
		try
		{
			out.writeObject(capsule);
			out.flush();	
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ; 
		
		try
		{
			retour = (Capsule)in.readObject();
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.getNombreLignesModifiees();
		
	}
	
	/**
	 * Supprimer l'instance de l'annonce passée en paramètre.
	 * 
	 * @param idASupprimer	ID de l'annonce à supprimer.
	 * @return				Retourne "true" si l'annonce a bien été supprimée, "false" sinon.
	 */
	public boolean supprimerAnnonce(Integer idASupprimer)
	{
		Capsule capsule = new Capsule();
		capsule.setRequeteSupprimerAnnonce(idASupprimer);
	
		try
		{
			out.writeObject(capsule);
			out.flush();	
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
		Capsule retour = null ; 
		
		try
		{
			retour = (Capsule)in.readObject();
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
		
		return retour.isFait();
	}
	
	
	
	/**
	 * Permet de vérifier si le département est aux bonnes normes.
	 * 
	 * @return "true" si le département est de la bonne forme, "false" sinon.
	 */
	public static boolean verifierDepartement(String departement)
	{
		// Départements autorisés : de 01 à 95, de 971 à 976 et 2A et 2B
		
		Pattern pattern = Pattern.compile("(^[0-9]{2}$)|(^97[1-6]{1}$)|(^2[abAB]{1}$)");
		
		Matcher matcher = pattern.matcher(departement);
		
		// Si ça passe la regex
		if(matcher.find())
		{
			// Et que le département est bien inférieur à 96
			if(Integer.parseInt(departement) < 96)
			{
				// Alors le département est aux normes
				return true;
			}
		}
		
		// Sinon on retourne false
		return false;
	}
	
	
	
	/**
	 * Permet de vérifier si le titre n'est pas trop long.
	 * 
	 * @param titre Le titre de l'annonce à vérifier.
	 * @return "true" si le titre fait moins de 255 caractères, "false" sinon.
	 */
	public static boolean verifierTitre(String titre)
	{
		if(titre.length() < 255)
		{
			return true;
		}
		
		return false;
	}
	
	
	
	/**
	 * Permet de vérifier si le numéro de téléphone est au bon format.
	 * 
	 * @param telephone Le numéro de téléphone à vérifier.
	 * @return "true" si le numéro est bon, "false" sinon.
	 */
	public static boolean verifierTelephone(String telephone)
	{
		Pattern pattern = Pattern.compile("(^0[0-9]{9}$)|(^\\+[0-9]{3,}$)");
		
		Matcher matcher = pattern.matcher(telephone);
		
		if(matcher.find())
		{
			return true;
		}
		
		return false;
	}
	
	
	
	/**
	 * Permet de vérifier si l'adresse mail est valide.
	 * 
	 * @param email L'email à vérifier.
	 * @return "true" si l'email est bon, "false" sinon.
	 */
	public static boolean verifierEmail(String email)
	{
		Pattern pattern = Pattern.compile("^[a-z0-9._-]+@[a-z0-9._-]+\\.[a-z]{2,4}$");
		
		Matcher matcher = pattern.matcher(email);
		
		if(matcher.find())
		{
			return true;
		}
		
		return false;
	}
	
	
	
	// GETTERS ET SETTERS 
	
	/**
	 * @return the vendeur
	 */
	public VendeurDTO getVendeurStocke()
	{
		return vendeurStocke;
	}



	/**
	 * @param vendeur the vendeur to set
	 */
	public void setVendeurStocke(VendeurDTO vendeur)
	{
		this.vendeurStocke = vendeur;
	}
	
	
	
	/**
	 * @return the annonceStocke
	 */
	public AnnonceDTO getAnnonceStocke()
	{
		return annonceStocke;
	}


	/**
	 * @param annonceStocke the annonceStocke to set
	 */
	public void setAnnonceStocke(AnnonceDTO annonceStocke)
	{
		this.annonceStocke = annonceStocke;
	}
}
